-
Notifications
You must be signed in to change notification settings - Fork 229
Optimize Date::to_calendar
#7089
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
528b4f9 to
dd1948c
Compare
dd1948c to
95a7211
Compare
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request optimizes date conversions between different calendar systems by introducing a more direct conversion path via RataDie for non-Gregorian calendars, while preserving the efficient ISO-based conversion for Gregorian ones. This is achieved by adding a HAS_CHEAP_ISO_CONVERSION constant to the Calendar trait. The changes are well-structured and result in cleaner, more efficient code, especially in the Indian calendar implementation. I've found one potential issue with handling overflow in the new Indian calendar conversion logic, which I've detailed in a comment. Overall, this is a great improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fairly neutral on whether this is an improvement or not. It deletes more lines of code than it adds so I'll approve based on that.
components/calendar/src/date.rs
Outdated
| let inner = | ||
| if A::Calendar::HAS_CHEAP_ISO_CONVERSION && A2::Calendar::HAS_CHEAP_ISO_CONVERSION { | ||
| let iso = self.calendar.as_calendar().to_iso(self.inner()); | ||
| calendar.as_calendar().from_iso(iso) | ||
| } else { | ||
| let rd = self.calendar.as_calendar().to_rata_die(self.inner()); | ||
| calendar.as_calendar().from_rata_die(rd) | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: I wonder if this PR would be better going through an intermediate such as
enum RataDieOrIso { RataDie(RataDie), Iso(Date<Iso>) }Calendars would return Iso if cheap or RataDie otherwise, and constructors would have code such as
// Calendar with cheap ISO conversion
let iso_date = match RataDieOrIso {
RataDieOrIso::RataDie(rata_die) => Iso.from_rata_die(rata_die),
RataDieOrIso::Iso(iso_date) => iso_date,
};
// Calendar with expensive ISO conversion
let rata_die = match RataDieOrIso {
RataDieOrIso::RataDie(rata_die) => rata_die,
RataDieOrIso::Iso(iso_date) => Iso.to_rata_die(iso_date),
};There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that ISO is only cheaper if it's cheap for both calendars, and your proposal only considers the source calendar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would work (consider Hebrew as expensive ISO and ROC as cheap ISO):
- Hebrew to Hebrew: converts through RataDie. OK.
- ROC to ROC: converts through ISO. OK, all cheap.
- Hebrew to ROC: Hebrew returns RataDie (expensive), then ROC converts it to ISO (casioneri), then ISO to ROC (cheap).
- ROC to Hebrew: ROC returns ISO (cheap), then Hebrew converts the ISO to RataDie (casioneri) and RataDie to Hebrew (expensive).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I guess that works. However, accepting RataDieOrIso means the code path being taken is not statically known, so more code gets linked and optimisations might be inhibited
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I posted another thought in #7089 (comment) which I think is better than the one in this thread.
You have absolutely no opinion on the work this avoids for conversions between non-Gregorian calendars? |
Well the narrower fix to that bug would be to make to_calendar unconditionally use RataDie, or to land the casioneri code so that rata die to ISO is very cheap. I haven't formed an opinion on whether the bigger refactor is worthwhile for implementing the change. |
I'm happy to do that, I thought this would be the narrower fix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this landing but let's get a third opinion from @Manishearth on the approach
components/calendar/src/calendar.rs
Outdated
| /// Whether `from_iso`/`to_iso` is more efficient | ||
| /// than `from_rata_die`/`to_rata_die`. | ||
| const HAS_CHEAP_ISO_CONVERSION: bool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Another design here would be
fn cheap_to_iso(&self, date: &Self::DateInner) -> Option<IsoDateInner>;
from_iso would retain the default impl as you have here. Then the Date code would be
match from_date.cheap_to_iso(inner) {
Some(iso_date) => to_calendar.from_iso(iso_date),
None => to_calendar.from_rata_die(from_date.to_rata_die())
}All of the cheap_to_iso impls should be marked as #[inline] so that DCE works nicely and avoids the branch.
I think all the pairs work:
- ROC to ROC: uses
cheap_to_isoandfrom_iso. OK - Hebrew to Hebrew: uses
to_rata_dieandfrom_rata_die. OK - ROC to Hebrew: uses
cheap_to_isoandfrom_iso, wherefrom_isointernally uses the default implto_rata_dieandfrom_rata_die. OK - Hebrew to ROC: uses
to_rata_dieandfrom_rata_die. OK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnyCalendarstill uses theDate<A> -> RataDie -> Date<Iso> -> RataDie -> Date<B>path, if we wanted to optimise this we'd needHAS_CHEAP_ISO_CONVERSIONto be a method instead of aconst.
I think the solution here is basically this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally not super worried about RD-ISO conversions, but sure, why not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed the part about AnyCalendar not benefiting from this. I think we should do a method in order for that to work. We have methods on Calendar for everything else that could be const specifically because we want AnyCalendar to work.
| } | ||
|
|
||
| const HAS_CHEAP_ISO_CONVERSION: bool = false; | ||
| fn has_cheap_iso_conversion(&self) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: mark all the methods as #[inline]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it shouldn't be necessary for in-crate usages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a huge fan of adding yet another Calendar API, but it's fine.
Currently non-Gregorian calendars implement
to_calendarasDate<A> -> RataDie -> Date<Iso> -> RataDie -> Date<B>. This PR cuts out the ISO conversions to make itDate<A> -> RataDie -> Date<B>.Gregorian calendars currently use
Date<A> -> Date<Iso> -> Date<B>, where both of these conversions are free because internally they are the same. This PR preserves this behaviour by adding aHAS_CHEAP_ISO_CONVERSIONconst to theCalendartrait, and if bothAandBhave this, it goes through ISO instead of RD.AnyCalendarstill uses theDate<A> -> RataDie -> Date<Iso> -> RataDie -> Date<B>path, if we wanted to optimise this we'd needHAS_CHEAP_ISO_CONVERSIONto be a method instead of aconst.